/*
 * Copyright 2009 Tran Nhut Trung.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package org.eclipse.core.databinding.conversion.gwt;

import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.NumberFormat;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.databinding.conversion.Converter;

public class Converters {
  public static final NumberFormat DEFAULT_NUMBER_FORMAT = NumberFormat.getDecimalFormat();
  public static final DateTimeFormat DEFAULT_DATE_FORMAT = DateTimeFormat.getLongDateFormat();
  
  private static Map<Tripple, Converter> converterMap = new HashMap<Tripple, Converter>();

  static{
    // Boolean conversion
    addConverter(Boolean.class, String.class, null, new BooleanToStringConverter());
    addConverter(String.class, Boolean.class, null, new StringToBooleanConverter());
    
    // Date conversion
    addConverter(Date.class, String.class, null, new DateToStringConverter(null));
    addConverter(String.class, Date.class, null, new StringToDateConverter(null));
    addConverter(java.sql.Date.class, String.class, null, new SQLDateToStringConverter(null));
    addConverter(String.class, java.sql.Date.class, null, new StringToSQLDateConverter(null));

    // Number conversion
    addConverter(Double.class, String.class, null, new NumberToStringConverter(Double.class, null));
    addConverter(Float.class, String.class, null, new NumberToStringConverter(Float.class, null));
    addConverter(Long.class, String.class, null, new NumberToStringConverter(Long.class, null));
    addConverter(Integer.class, String.class, null, new NumberToStringConverter(Integer.class, null));
    addConverter(Short.class, String.class, null, new NumberToStringConverter(Short.class, null));
    addConverter(Byte.class, String.class, null, new NumberToStringConverter(Byte.class, null));
    
    addConverter(String.class, Double.class, null, new StringToNumberConverter(Double.class, null));
    addConverter(String.class, Float.class, null, new StringToNumberConverter(Float.class, null));
    addConverter(String.class, Long.class, null, new StringToNumberConverter(Long.class, null));
    addConverter(String.class, Integer.class, null, new StringToNumberConverter(Integer.class, null));
    addConverter(String.class, Short.class, null, new StringToNumberConverter(Short.class, null));
    addConverter(String.class, Byte.class, null, new StringToNumberConverter(Byte.class, null));

    // TODO: more converters
  }
  
  private static class Tripple{
    private Class fromType;
    private Class toType;
    private Object format;
    private Tripple(Class fromType, Class toType, Object format){
      this.fromType = fromType;
      this.toType = toType;
      this.format = format;
    }
    public boolean equals(Object o){
      if (!(o instanceof Tripple)){
        return false;
      }
      Tripple other = (Tripple)o;
      
      boolean formatEquals = false;
      if ((format != null) && (other.format != null)){
        formatEquals = format.equals(other.format);
      }
      if ((format == null) && (other.format == null)){
        formatEquals = true;
      }
      
      return other.fromType.equals(fromType) && other.toType.equals(toType) && formatEquals;
    }
    
    public int hashCode(){
      return 7*fromType.hashCode() + 5*toType.hashCode() + (format != null ? format.hashCode() : 0);
    }
  }
  
  public static Object convert(Class fromType, Class toType, Object format, Object fromObject){
    return getConverter(fromType, toType, format).convert(fromObject);
  }
  
  public static void addConverter(Class fromType, Class toType, Converter converter){
    addConverter(fromType, toType, null, converter);
  }
  public static void addConverter(Class fromType, Class toType, Object format, Converter converter){
    Tripple tripple = new Tripple(fromType, toType, format);
    converterMap.put(tripple, converter);
  }
  public static Converter getConverter(Class fromType, Class toType){
    return getConverter(fromType, toType, null);
  }
  public static Converter getConverter(Class fromType, Class toType, Object format){
    Tripple tripple = new Tripple(fromType, toType, format);
    Converter converter = converterMap.get(tripple);

    // fallback to null format if could not found converter for specified format
    if ((converter == null) && (format != null)){
      return getConverter(fromType, toType);
    }
    
    // Find the super class
    if ((converter == null) && (format == null)){
      for (Tripple tripple2 : converterMap.keySet()){
        if (isSuperClass(tripple2.fromType, fromType) && 
            isSuperClass(tripple2.fromType, fromType)){
          return converterMap.get(tripple2);
        }
      }
    }

    return converter;
  }
  
  // FIXME: GWT does not has Class.isAssignableFrom method
  private static boolean isSuperClass(Class superClass, Class thisClass){
    if (thisClass.getSuperclass() != null){
      if (thisClass.getSuperclass() == superClass){
        return true;
      }else{
        return isSuperClass(superClass, thisClass.getSuperclass());
      }
    }
    return false;
  }
}
